#include "../../config.h"

#include <glibmm/i18n.h>

#include "../application.h"
#include "../log.h"
#include "../sound.h"
#include "p_widgets.h"

namespace gpyp {
namespace pref {

//========================
// FileEntry
void FileEntry::on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context,
                                      int x, int y,
                                      const Gtk::SelectionData &selection_data,
                                      guint info, guint time) {
  try {
    Glib::StringArrayHandle uris = selection_data.get_uris();
    if (uris.size()) {
      set_text(Glib::filename_from_uri(*uris.begin()));
      return;
    }
  } catch(const Glib::Exception &ex) {
    log::err << ex.what() << log::endl;
  }
  Gtk::Entry::on_drag_data_received(context, x, y,
                                    selection_data, info, time);
}

FileEntry::FileEntry() {
  drag_dest_set(Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_LINK);
  drag_dest_add_uri_targets();
}

//========================
// SoundBox
void SoundBox::volume_on_changed() {
  set_volume(*volume_);
}

void SoundBox::open_button_on_clicked() {
  if (!file_dialog_) {
    file_dialog_.reset(new Gtk::FileChooserDialog(parent_, _("Open"), Gtk::FILE_CHOOSER_ACTION_OPEN));

    Gtk::FileFilter filter;
#ifdef USE_VORBIS
    filter.add_pattern("*.ogg");
#endif
    filter.add_pattern("*.wav");
    filter.set_name(_("Supported sound file"));
    file_dialog_->set_filter(filter);
    file_dialog_->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    file_dialog_->add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
  }
  file_dialog_->set_filename(sound_entry_.get_text());
  file_dialog_->show();
}
void SoundBox::play_button_on_toggled() {
  if (play_button_.get_active()) {
    if (!sound_play()) {
      conn_play_button_toggled_.block();
      play_button_.set_active(false);
      conn_play_button_toggled_.unblock();
    }
  } else {
    sound_stop();
  }
}
#ifdef GTK_HAVE_VOLUME_BUTTON
void SoundBox::static_volume_button_on_value_changed(GtkScaleButton */*button*/,
                                                     gdouble         value,
                                                     gpointer        user_data) {
  ((SoundBox *)user_data)->volume_button_on_value_changed(value);
}
void SoundBox::volume_button_on_value_changed(double value) {
  if (volume_) volume_ = value;
}
#else
void SoundBox::volume_spin_on_value_changed() {
  if (volume_) volume_ = volume_spin_.get_value();
}
#endif
void SoundBox::file_dialog_on_response(int response_id) {
  if (response_id == Gtk::RESPONSE_OK) {
    sound_entry_.set_text(file_dialog_->get_filename());
  }
}

double SoundBox::get_volume() {
#ifdef GTK_HAVE_VOLUME_BUTTON
  return gtk_scale_button_get_value(GTK_SCALE_BUTTON(volume_button_));
#else
  return volume_spin_.get_value();
#endif
}
void SoundBox::set_volume(double volume) {
#ifdef GTK_HAVE_VOLUME_BUTTON
  gtk_scale_button_set_value(GTK_SCALE_BUTTON(volume_button_), volume);
#else
  return volume_spin_.set_value(volume);
#endif
}

bool SoundBox::sound_play() {
  play_id_ = sound::play(sound_entry_.get_text(), get_volume(),
                         sigc::mem_fun(*this, &SoundBox::sound_on_error),
                         sigc::mem_fun(*this, &SoundBox::sound_on_complete));
  return play_id_ != 0;
}
void SoundBox::sound_stop() {
  sound::stop(play_id_);
}
void SoundBox::sound_on_error(const Glib::ustring &error_message) {
  conn_play_button_toggled_.block();
  play_button_.set_active(false);
  conn_play_button_toggled_.unblock();
  log::err << error_message << log::endl;
}
void SoundBox::sound_on_complete() {
  conn_play_button_toggled_.block();
  play_button_.set_active(false);
  conn_play_button_toggled_.unblock();
}

void SoundBox::set(conf::RefAttributeStr    sound,
                   conf::RefAttributeDouble volume) {
  unset();
  sound_  = sound;
  volume_ = volume;
  if (sound_) {
    sound_->attach(sound_entry_.property_text());
  }
  volume_on_changed();
  conn_volume_on_changed_ = volume_->signal_changed()
    .connect(sigc::mem_fun(*this, &SoundBox::volume_on_changed));
}
void SoundBox::unset() {
  if (sound_) {
    sound_->detach(sound_entry_.property_text());
  }
  conn_volume_on_changed_.disconnect();
  sound_ .reset();
  volume_.reset();
}

SoundBox::SoundBox(Application &/*app*/, Gtk::Window &parent) :
  Gtk::HBox     (false, 2),
  parent_       (parent),
  open_button_  (Gtk::Stock::OPEN),
#if GTK_CHECK_VERSION(2, 12, 0)
  play_button_  (Gtk::Stock::MEDIA_PLAY)
#else
  play_button_  (_("Play"))
#endif
#ifdef GTK_HAVE_VOLUME_BUTTON
  ,
  volume_button_(GTK_VOLUME_BUTTON(gtk_volume_button_new()))
#endif
{
  open_button_.signal_clicked()
    .connect(sigc::mem_fun(*this, &SoundBox::open_button_on_clicked));
  conn_play_button_toggled_ = play_button_.signal_toggled()
    .connect(sigc::mem_fun(*this, &SoundBox::play_button_on_toggled));

#ifdef GTK_HAVE_VOLUME_BUTTON
  g_object_set(G_OBJECT(volume_button_),
               "size", GTK_ICON_SIZE_MENU,
               NULL);

  GtkAdjustment *adj =
    gtk_scale_button_get_adjustment(GTK_SCALE_BUTTON(volume_button_));
  g_object_set(G_OBJECT(adj),
               "lower", 0.0,
               "upper", 1.0,
               NULL);

  gtk_button_set_relief(GTK_BUTTON(volume_button_), GTK_RELIEF_NORMAL);
  g_signal_connect(G_OBJECT(volume_button_), "value-changed",
                   G_CALLBACK(SoundBox::static_volume_button_on_value_changed),
                   (gpointer)this);
#else
  volume_spin_.set_range(0, 1);
  volume_spin_.set_digits(2);
  volume_spin_.set_alignment(Gtk::ALIGN_RIGHT);
  volume_spin_.signal_value_changed()
    .connect(sigc::mem_fun(*this, &SoundBox::volume_spin_on_value_changed));
#endif

  file_dialog_.signal_response().connect(sigc::mem_fun(*this, &SoundBox::file_dialog_on_response));

  pack_start(sound_entry_);
  pack_start(open_button_, false, true);
  pack_start(play_button_, false, true);
#ifdef GTK_HAVE_VOLUME_BUTTON
  gtk_box_pack_start(GTK_BOX(gobj()), GTK_WIDGET(volume_button_), FALSE, TRUE, 0);
#else
  pack_start(volume_spin_, false, true);
#endif
  show_all();
}
SoundBox::~SoundBox() {
  unset();
}

//========================
// ColorComboBox
Gtk::TreeIter ColorComboBox::get_iter(Color color) {
  if (!color) return none_ ? iter_none_ : Gtk::TreeIter();

  Gtk::TreeIter iter = iter_separator_;
  for(++iter; iter; ++iter) {
    if (*get_color(iter)->color() == color) {
      return iter;
    }
  }

  if (color == custom_color_) return iter_custom_;
  return Gtk::TreeIter();
}
Gtk::TreeIter ColorComboBox::get_iter(ConfMain::RefPresetColor color) {
  Gtk::TreeNodeChildren children = liststore_->children();
  for(Gtk::TreeIter iter = children.begin(); iter != children.end(); ++iter) {
    if (get_color(iter) == color) {
      return iter;
    }
  }
  return Gtk::TreeIter();
}
ConfMain::RefPresetColor ColorComboBox::get_color(Gtk::TreeIter iter) {
  return iter ? iter->get_value(column_color_) : ConfMain::RefPresetColor();
}

void ColorComboBox::add_color(ConfMain::RefPresetColor preset_color) {
  liststore_->append()->set_value(column_color_, preset_color);
  attr_on_changed();
}
void ColorComboBox::remove_color(ConfMain::RefPresetColor preset_color) {
  Gtk::TreeIter iter = get_iter(preset_color);
  Gtk::TreeIter active = get_active();
  if (active && active == iter) {
    custom_color_ = *get_color(iter)->color();
    block_changed_ = true;
    set_active(iter_custom_);
    block_changed_ = false;
  }
  liststore_->erase(iter);
  attr_on_changed();
}
void ColorComboBox::clear() {
  iter_none_      = Gtk::TreeIter();
  iter_custom_    = Gtk::TreeIter();
  liststore_->clear();
  if (none_) iter_none_ = liststore_->append();
  iter_custom_    = liststore_->append();
  iter_separator_ = liststore_->append();
  iter_separator_->set_value(column_separator_, true);
  attr_on_changed();
}

bool
ColorComboBox::separator_func(const Glib::RefPtr<Gtk::TreeModel> &/*model*/,
                              const Gtk::TreeIter                &iter) {
  return iter->get_value(column_separator_);
}
void ColorComboBox::on_cell_data(const Gtk::TreeIter &iter) {
  if (!iter) {
    renderer_.property_text()                   = Glib::ustring();
    renderer_.property_cell_background_set()    = false;
    return;
  }

  if (none_ && iter == iter_none_) {
    renderer_.property_text()                   = _("None");
    renderer_.property_cell_background_set()    = false;
  } else if (iter == iter_custom_) {
    renderer_.property_text()                   = _("Custom");
    renderer_.property_cell_background_set()    = true;
    renderer_.property_cell_background_gdk()    = custom_color_;
  } else {
    renderer_.property_text()                   = Glib::ustring();

    ConfMain::RefPresetColor pc = get_color(iter);
    if (pc) {
      Color c = *pc->color();
      renderer_.property_cell_background_gdk()  = c;
      renderer_.property_cell_background_set()  = c;
    }
  }
}

void ColorComboBox::init_custom_dialog() {
  if (!custom_dialog_) {
    custom_dialog_.reset(new Gtk::ColorSelectionDialog());
    custom_dialog_->set_transient_for(parent_);
  }
  Gtk::ColorSelection *sel = custom_dialog_->get_colorsel();
  sel->set_current_color(custom_color_);
}

void ColorComboBox::on_changed() {
  if (!block_changed_) {
    Gtk::TreeIter iter = get_active();
    if (iter) {
      if (iter == iter_custom_) {
        init_custom_dialog();
        custom_dialog_->show();
      }
      if (attr_) {
        conn_attr_changed_.block();
        if (none_ && iter == iter_none_) {
          attr_ = Color();
        } else if (iter == iter_custom_) {
          attr_ = custom_color_;
        } else {
          attr_ = *get_color(iter)->color();
        }
        conn_attr_changed_.unblock();
      }
    }
  }
  Gtk::ComboBox::on_changed();
}
void ColorComboBox::attr_on_changed() {
  if (!get_model()) return;
  if (!attr_ || !*attr_) {
    block_changed_ = true;
    if (none_) {
      set_active(iter_none_);
    } else {
      Color black("black");
      Gtk::TreeIter iter = get_iter(black);
      if (iter) {
        set_active(iter);
      } else {
        custom_color_ = black;
        set_active(iter_custom_);
      }
    }
    block_changed_ = false;
    queue_draw();
    return;
  }
  Gtk::TreeIter iter = iter_separator_;
  for(++iter; iter; ++iter) {
    if (*get_color(iter)->color() == *attr_) {
      block_changed_ = true;
      set_active(iter);
      block_changed_ = false;
      return;
    }
  }
  custom_color_ = *attr_;
  block_changed_ = true;
  set_active(iter_custom_);
  block_changed_ = false;
  queue_draw();
}

void ColorComboBox::custom_dialog_on_response(int response_id) {
  if (response_id == Gtk::RESPONSE_OK) {
    Gtk::ColorSelection *sel = custom_dialog_->get_colorsel();
    custom_color_ = sel->get_current_color();
    attr_->set_value(custom_color_);
  }
}

void ColorComboBox::set(RefAttributeColor attr) {
  unset();
  if (attr) {
    attr_ = attr;
    attr_on_changed();
    conn_attr_changed_ = attr_->signal_changed()
      .connect(sigc::mem_fun(*this, &ColorComboBox::attr_on_changed));
  }
}
void ColorComboBox::unset() {
  conn_attr_changed_.disconnect();
  attr_.reset();
}

void ColorComboBox::add_preset() {
  if (get_active() == iter_custom_) {
    if (get_iter(custom_color_) == iter_custom_) {
      ConfMain::RefPresetColor color = app_.conf().preset_colors()->add_item(custom_color_);
      set_active(get_iter(color));
    }
  }
}
void ColorComboBox::remove_preset() {
  Gtk::TreeIter iter = get_active();
  if ((!none_ || iter != iter_none_) &&
      iter != iter_custom_) {
    ConfMain::RefPresetColor color = get_color(iter);
    app_.conf().preset_colors()->remove_item(color);
  }
}

void ColorComboBox::show_custom_dialog() {
  init_custom_dialog();
  custom_dialog_->show();
}

ColorComboBox::ColorComboBox(Application      &app,
                             Gtk::Window      &parent,
                             RefAttributeColor attr,
                             bool              none) :
  app_          (app),
  parent_       (parent),
  none_         (none),
  custom_color_ ("white"),
  block_changed_(false)
{
  columns_.add(column_color_);
  columns_.add(column_separator_);
  liststore_ = Gtk::ListStore::create(columns_);

  clear();
  ConfMain::RefPresetColors cols = app.conf().preset_colors();
  for(ConfMain::PresetColors::iterator iter = cols->begin();
      iter != cols->end(); ++iter) {
    add_color(*iter);
  }
  cols->signal_item_added()
    .connect(sigc::mem_fun(*this, &ColorComboBox::add_color));
  cols->signal_item_removed()
    .connect(sigc::mem_fun(*this, &ColorComboBox::remove_color));
  cols->signal_cleared()
    .connect(sigc::mem_fun(*this, &ColorComboBox::clear));

  set_model(liststore_);
  pack_start(renderer_);
  set_row_separator_func(sigc::mem_fun(*this, &ColorComboBox::separator_func));
  set_cell_data_func(renderer_, sigc::mem_fun(*this, &ColorComboBox::on_cell_data));

  custom_dialog_.signal_response()
    .connect(sigc::mem_fun(*this, &ColorComboBox::custom_dialog_on_response));

  set(attr);
}
ColorComboBox::~ColorComboBox() {
  unset();
}

//========================
// ColorBox::ArrowButton
ColorBox::ArrowButton::ArrowButton() :
  arrow_(Gtk::ARROW_DOWN, Gtk::SHADOW_NONE)
{
  set_image(arrow_);
}

//========================
// ColorBox
void ColorBox::calc_popup_menu_position(int &x, int &y, bool &/*push_in*/) {
  button_.get_window()->get_origin(x, y);

  Gtk::Allocation a = button_.get_allocation();
  //Gtk::Requisition r = menu_.size_request();
  x += a.get_x();
  //x += button_.get_allocation().get_width();
  //x -= r.width;

  y += a.get_y();
  y += a.get_height();
}

void ColorBox::button_on_clicked() {
  menu_.popup(sigc::mem_fun(*this, &ColorBox::calc_popup_menu_position),
              0, gtk_get_current_event_time());
}

void ColorBox::combobox_on_changed() {
  mi_add_   .set_sensitive(combobox_.get_can_add());
  mi_remove_.set_sensitive(combobox_.get_can_remove());
}

void ColorBox::set(RefAttributeColor attr) {
  combobox_.set(attr);
}
void ColorBox::unset() {
  combobox_.unset();
}

ColorBox::ColorBox(Application      &app,
                   Gtk::Window      &parent,
                   RefAttributeColor attr,
                   bool              none) :
  Gtk::HBox (false, 2),
  combobox_ (app, parent, attr, none),
  mi_add_   (_("_Add to preset"),
             sigc::mem_fun(combobox_, &ColorComboBox::add_preset),
             Gtk::Stock::ADD),
  mi_remove_(_("_Remove from preset"),
             sigc::mem_fun(combobox_, &ColorComboBox::remove_preset),
             Gtk::Stock::REMOVE)
{
  menu_.append(mi_add_);
  menu_.append(mi_remove_);
  menu_.show_all();

  Gtk::Image *cp = new Gtk::Image(IconManager::load_icon(IconManager::COLOR_PICKER,
                                                         GTK_ICON_SIZE_SMALL_TOOLBAR));
  pick_color_.set_image(*Gtk::manage(cp));

  pack_start(combobox_);
  pack_start(pick_color_, false, false);
  pack_start(button_,     false, false);
  show_all();

  combobox_  .signal_changed().connect(sigc::mem_fun(*this,     &ColorBox::combobox_on_changed));
  pick_color_.signal_clicked().connect(sigc::mem_fun(combobox_, &ColorComboBox::show_custom_dialog));
  button_    .signal_clicked().connect(sigc::mem_fun(*this,     &ColorBox::button_on_clicked));
}

#ifndef GTK_HAVE_CELL_RENDERER_ACCEL
//========================
// CellRendererAccel::Dialog
void CellRendererAccel::Dialog::update_label() {
  if (grabbing_) {
    label_.set_text(_("New accelerator..."));
  } else if (accel_key_) {
    label_.set_text(AttributeAccel::name(accel_key_, accel_mods_));
  } else {
    label_.set_text(_("Disabled"));
  }
}

void CellRendererAccel::Dialog::grab_on_clicked() {
  if (get_window()->keyboard_grab(false, GDK_CURRENT_TIME) != Gdk::GRAB_SUCCESS) return;
  if (get_window()->pointer_grab (false, Gdk::BUTTON_PRESS_MASK, GDK_CURRENT_TIME) != Gdk::GRAB_SUCCESS) {
    Gdk::Window::keyboard_ungrab(GDK_CURRENT_TIME);
    return;
  }
  grabbing_ = true;
  update_label();
}
void CellRendererAccel::Dialog::clear_on_clicked() {
  changed_          = true;
  cleared_          = true;
  accel_key_        = 0;
  accel_mods_       = (Gdk::ModifierType)0;
  hardware_keycode_ = 0;
  update_label();
}

bool CellRendererAccel::Dialog::on_key_press_event(GdkEventKey *event) {
  if (grabbing_) {
    guint accel_key = gdk_keyval_to_lower(event->keyval);
    Gdk::ModifierType accel_mods = (Gdk::ModifierType)0;
    Gdk::ModifierType consumed_mods = (Gdk::ModifierType)0;
    gdk_keymap_translate_keyboard_state(get_display()->get_keymap(),
                                        event->hardware_keycode,
                                        (GdkModifierType)event->state,
                                        event->group,
                                        NULL, NULL, NULL,
                                        (GdkModifierType *)&consumed_mods);

    if (accel_key == GDK_ISO_Left_Tab) accel_key = GDK_Tab;

    accel_mods  = (Gdk::ModifierType)event->state & Gtk::AccelGroup::get_default_mod_mask();
    accel_mods &= ~consumed_mods;

    if (accel_key != event->keyval) accel_mods |= Gdk::SHIFT_MASK;

    if (Gtk::AccelGroup::valid(accel_key, accel_mods)) {
      get_display()->keyboard_ungrab(event->time);
      get_display()->pointer_ungrab(event->time);

      accel_key_        = accel_key;
      accel_mods_       = accel_mods;
      hardware_keycode_ = event->hardware_keycode;

      grabbing_ = false;
      changed_  = true;
      cleared_  = false;
      update_label();
      return true;
    }
  }
  return Gtk::Dialog::on_key_press_event(event);
}

CellRendererAccel::Dialog::Dialog(guint key, Gdk::ModifierType mods) :
  label_           (AttributeAccel::name(key, mods)),
  button_grab_     (_("Grab")),
  button_clear_    (_("Clear")),
  changed_         (false),
  cleared_         (false),
  accel_key_       (key),
  accel_mods_      (mods),
  hardware_keycode_(0),
  grabbing_        (false)
{
  update_label();

  button_grab_.signal_clicked()
    .connect(sigc::mem_fun(*this, &CellRendererAccel::Dialog::grab_on_clicked));
  button_clear_.signal_clicked()
    .connect(sigc::mem_fun(*this, &CellRendererAccel::Dialog::clear_on_clicked));

  hbox_.pack_start(label_,        true,  true);
  hbox_.pack_start(button_grab_,  false, false);
  hbox_.pack_start(button_clear_, false, false);
  hbox_.show_all();

  get_vbox()->pack_start(hbox_, false, false);

  add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
  add_button(Gtk::Stock::OK,     Gtk::RESPONSE_OK);
}

//========================
// CellRendererAccel
Gtk::CellEditable *
CellRendererAccel::start_editing_vfunc(GdkEvent              */*event*/,
                                       Gtk::Widget           &/*widget*/,
                                       const Glib::ustring   &path,
                                       const Gdk::Rectangle  &/*background_area*/,
                                       const Gdk::Rectangle  &/*cell_area*/,
                                       Gtk::CellRendererState /*flags*/) {
  Dialog d(accel_key_, accel_mods_);
  if (d.run() == Gtk::RESPONSE_OK) {
    if (d.cleared()) {
      signal_accel_cleared_(path);
    }
    if (d.changed()) {
      signal_accel_edited_(path, d.accel_key(), d.accel_mods(),
                           d.hardware_keycode());
    }
  }
  return NULL;
}

void CellRendererAccel::set(guint key, Gdk::ModifierType mods) {
  accel_key_  = key;
  accel_mods_ = mods;
  if (accel_key_) {
    property_text() = Gtk::AccelGroup::get_label(accel_key_, accel_mods_);
  } else {
    property_text() = _("Disabled");
  }
}

CellRendererAccel::CellRendererAccel() {
}
#endif

//========================
// TriCheckButton
bool TriCheckButton::on_button_press_event(GdkEventButton *event) {
  if (event->type == GDK_BUTTON_PRESS &&
      event->button == 1) {
    if (*attr_) {
      attr_->set_value(false);
    } else if (!*attr_) {
      attr_ = boost::logic::indeterminate;
    } else {
      attr_->set_value(true);
    }
  }
  return true;
}

void TriCheckButton::attr_on_changed() {
  if (*attr_) {
    set_active(true);
    set_inconsistent(false);
  } else if (!*attr_) {
    set_active(false);
    set_inconsistent(false);
  } else {
    set_inconsistent(true);
  }
}

void TriCheckButton::set(conf::RefAttributeTribool attr) {
  unset();
  attr_ = attr;
  attr_on_changed();
  conn_attr_changed_ = attr_->signal_changed()
    .connect(sigc::mem_fun(*this, &TriCheckButton::attr_on_changed));
}
void TriCheckButton::unset() {
  conn_attr_changed_.disconnect();
  attr_ = conf::RefAttributeTribool();
}

TriCheckButton::TriCheckButton(const Glib::ustring &label) :
  Gtk::CheckButton(label)
{
  add_events(Gdk::BUTTON_PRESS_MASK);
}

}/*pref*/
}/*gpyp*/
